/**
 * 
 */
package cz.cuni.mff.abacs.burglar.visual.play_state;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Polygon;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.state.StateBasedGame;

import cz.cuni.mff.abacs.burglar.logics.objects.agents.Agent;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Container;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Door;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Position;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Switch;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Vender;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import cz.cuni.mff.abacs.burglar.visual.Main;


/**
 * Contains the graphics representation of the {@link PlayState}.
 * 
 * @author abacs
 *
 */
public class GraphicPlayState extends InterfacedPlayState {
	// constants:
	
	/** Movement directions on the map. */
	public static enum Direction {
		NORTH, SOUTH, WEST, EAST, NORTH_WEST, SOUTH_WEST, NORTH_EAST, SOUTH_EAST
	};
	
	private static String IMAGE_PATH = Main.RESOURCE_PATH + "img/";
	
	private static int SCALE = 1;
	
	/**  */
	private static Color COLOR_FILTER_KNOWN = Color.white;
	/**  */
	private static Color COLOR_FILTER_UNKNOWN = Color.darkGray;
	/**  */
	private static Color COLOR_FILTER_MISTAKEN = Color.lightGray;
	
	
	// -------------------------------------------------------------------------
	
	
	private Image _imgBurglar = null;
	private Image _imgGuard = null;
	
	private Image _imgContainerClosed = null;
	private Image _imgContainerOpened = null;
	private Image _imgDoorClosed = null;
	private Image _imgDoorOpened = null;
	private Image _imgFloor = null;
	private Image _imgPhone = null;
	private Image _imgCamera = null;
	private Image _imgSelection = null;
	private Image _imgSwitchOff = null;
	private Image _imgSwitchOn = null;
	private Image _imgVenderOff = null;
	private Image _imgVenderOn = null;
	private Image _imgWall = null;
	
	private Agent _currentlySelectedAgent = null;
	
	
	// -------------------------------------------------------------------------
	// constructors:
	
	
	/**
	 * 
	 * 
	 * @param stateID
	 * @throws SlickException
	 */
	public GraphicPlayState(int stateID)
			throws SlickException {
		super(stateID);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/** 
	 * Initialize any needed data before the game loop.
	 * 
	 * @param container
	 * @param game
	 */
	@Override
	public void init(GameContainer container, StateBasedGame game)
			throws SlickException {
		super.init(container, game);
		
		this._imgBurglar = new Image(IMAGE_PATH + "burglar.gif");
		this._imgGuard = new Image(IMAGE_PATH + "guard.gif");
		
		this._imgContainerClosed = 
			new Image(IMAGE_PATH + "container_closed.gif");
		this._imgContainerOpened = 
			new Image(IMAGE_PATH + "container_opened.gif");
		this._imgDoorClosed = new Image(IMAGE_PATH + "door_closed.gif");
		this._imgDoorOpened = new Image(IMAGE_PATH + "door_opened.gif");
		this._imgFloor = new Image(IMAGE_PATH + "floor.gif");
		this._imgPhone = new Image(IMAGE_PATH + "phone.gif");
		this._imgCamera = new Image(IMAGE_PATH + "camera.png");
		this._imgSelection = new Image(IMAGE_PATH + "selection.gif");
		this._imgSwitchOff = new Image(IMAGE_PATH + "switch_off.gif");
		this._imgSwitchOn = new Image(IMAGE_PATH + "switch_on.gif");
		this._imgVenderOff = new Image(IMAGE_PATH + "vender_off.gif");
		this._imgVenderOn = new Image(IMAGE_PATH + "vender_on.gif");
		this._imgWall = new Image(IMAGE_PATH + "wall.gif");
	}
	
	
	/**
	 *  Allows to draw the world.
	 *  
	 *  @param container
	 *  @param game
	 *  @param graphics
	 *  @throws SlickException
	 */
	@Override
	public void render(
			GameContainer container,
			StateBasedGame game,
			Graphics graphics
	) throws SlickException {
		try{
			// draw the map:
			this.drawMap(graphics);
			// selection:
			this.drawSelection();
		}catch(Exception e){
			// invalid game map
			System.err.println(e.toString());
		}
		super.render(container, game, graphics);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * 
	 * 
	 * @param graphics
	 */
	protected void drawMap(Graphics graphics) {
		// draw wall:
		for(int x = 0; x < this._screen._width; x++){
			for(int y = 0; y < this._screen._height; y++){
				this._imgWall.draw(
						x * Main.RESOURCE_BLOCK_SIZE,
						y * Main.RESOURCE_BLOCK_SIZE,
						GraphicPlayState.SCALE
				);
			}
		}
		
		if(this._map == null)
			return;
		if(this._currentlySelectedAgent == null)
			this._currentlySelectedAgent = this._map.getBurglar();
		
		// draw all the positions:
		for(Position pos : this._map.getPositions())
			this.drawPosition(pos, this._currentlySelectedAgent, graphics);
		
		// TODO remove drawing duplication
		
		for(Position pos : this._map.getOperablePositions())
			this.drawPosition(pos, this._currentlySelectedAgent, graphics);
		
		// draw the intent line:
		this.drawAgentIntent(
				this._currentlySelectedAgent,
				this._intentDistance,
				// TODO temporal color change
				org.newdawn.slick.Color.black,//org.newdawn.slick.Color.red,
				graphics
		);
		
//		if(this._drawGuardIntents){
//			for(Agent agent : this._map.getGuards())
//				this.drawAgentIntent(
//						agent,
//						this._intentDistance,
//						org.newdawn.slick.Color.gray,
//						graphics
//				);
//		}
		
		for(Agent agent : this._map.getAgents())
			this.drawAgent(agent, this._currentlySelectedAgent);
	}
	
	
	/**
	 * Non-filtered means the burglar knows the position perfectly.
	 * Light grey filter means the burglar knows the position, 
	 * but misses some detail.
	 * Dark grey filter means the burglar does not know the position at all.
	 * 
	 * 
	 * @param position the position to draw
	 * @param viewingAgent the agent that's belief base is visualized
	 * @param graphics 
	 */
	protected void drawPosition(
			Position position,
			Agent viewingAgent,
			Graphics graphics
	) {
		if(! this.positionInScreen(position))
			return;
		
		int x = this._screen.mapXToScreenX(position.getX());
		int y = this._screen.mapYToScreenY(position.getY());
		
		// the filter indicates the whether the burglar agent knows the position
		Color filter = COLOR_FILTER_UNKNOWN;
		if(viewingAgent.getBeliefBase().knowsPosition(position)){
			if(viewingAgent.getBeliefBase().matches(position)){
				filter = COLOR_FILTER_KNOWN;
			}else{
				filter = COLOR_FILTER_MISTAKEN;
			}
		}
		
		// draws a default floor background
		this._imgFloor.draw(x, y, GraphicPlayState.SCALE);
		
		switch(position.getType()){
		case CAMERA:
			this._imgCamera.draw(x, y, GraphicPlayState.SCALE, filter);
			break;
		case CONTAINER:
			if(((Container)position).isClosed())
				this._imgContainerClosed.draw(x, y, GraphicPlayState.SCALE, filter);
			else
				this._imgContainerOpened.draw(x, y, GraphicPlayState.SCALE, filter);
			break;
		case DOOR:
			if(((Door)position).isClosed())
				this._imgDoorClosed.draw(x, y - 16, GraphicPlayState.SCALE, filter);
			else
				this._imgDoorOpened.draw(x, y - 16, GraphicPlayState.SCALE, filter);
			break;
		case FLOOR:
			// floor background already drawn
			break;
		case PHONE:
			this._imgPhone.draw(x, y, GraphicPlayState.SCALE);
			break;
		case SWITCH:
			if(((Switch)position).isActive())
				this._imgSwitchOn.draw(x, y, GraphicPlayState.SCALE);
			else
				this._imgSwitchOff.draw(x, y, GraphicPlayState.SCALE);
			break;
		case VENDER:
			if(((Vender)position).hasDropped())
				this._imgVenderOn.draw(x, y - 16, GraphicPlayState.SCALE);
			else
				this._imgVenderOff.draw(x, y - 16, GraphicPlayState.SCALE);
			break;
		default:
			
		}
	}
	
	
	/**
	 * 
	 * @param agent
	 * @param viewingAgent the agent that's belief base is visualized
	 */
	protected void drawAgent(Agent agent, Agent viewingAgent) {
		Position position = agent.getPosition();
		if(! this.positionInScreen(position))
			return;
				
		int x = this._screen.mapXToScreenX(position.getX());
		int y = this._screen.mapYToScreenY(position.getY());
		
		Color filter = COLOR_FILTER_UNKNOWN;
		if(agent == viewingAgent)
			filter = COLOR_FILTER_KNOWN;
		
		switch(agent.getType()){
		case BURGLAR:
			this._imgBurglar.draw(x, y - 16, GraphicPlayState.SCALE, filter);
			break;
		case DOG:
			// TODO dog
			break;
		case GUARD:
			this._imgGuard.draw(x, y - 16, GraphicPlayState.SCALE, filter);
			break;
		default:
		}
	}
	
	
	/**
	 * Agent intent line to draw.
	 * 
	 * @param viewingAgent 
	 * @param distance number of steps to show. if less than 0, shows all of them.
	 * @param lineColor
	 * @param graphics 
	 */
	protected void drawAgentIntent(
			Agent viewingAgent,
			int distance,
			Color lineColor,
			Graphics graphics
	) {
		if(viewingAgent == null)
			return;
		
		Position agentPos = viewingAgent.getPosition();
		Position newPos;
		
		int counter = 0;
		
		for(Instruction instruction : viewingAgent.getInstructions()){
			
			if(counter++ == distance)
				break;
			
			graphics.setColor(lineColor);
			
			switch(instruction._code){
			case MOVE:
				newPos = this._map.getPosition(instruction._subjectId);
				
				if(		this.positionInScreen(agentPos) &&
						this.positionInScreen(newPos)
				){
					int deltaX = Main.RESOURCE_BLOCK_SIZE / 2;
					int deltaY = Main.RESOURCE_BLOCK_SIZE / 2;
					
					int x1 = this._screen.mapXToScreenX(agentPos.getX()) + deltaX;
					int y1 = this._screen.mapYToScreenY(agentPos.getY()) + deltaY;
					int x2 = this._screen.mapXToScreenX(newPos.getX()) + deltaX;
					int y2 = this._screen.mapYToScreenY(newPos.getY()) + deltaY;
					
					int circleSize = Main.RESOURCE_BLOCK_SIZE / 8;
					
					graphics.drawOval(
							x2 - circleSize,
							y2 - circleSize,
							circleSize * 2,
							circleSize * 2
					);
					
					if(agentPos.getX() < newPos.getX()){
						if(agentPos.getY() < newPos.getY()){
							drawArrow(
									x1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									y1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.SOUTH_EAST,
									graphics
							);
						}
						if(agentPos.getY() == newPos.getY()){
							drawArrow(
									x1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									y1,
									circleSize,
									Direction.EAST,
									graphics
							);
						}
						if(agentPos.getY() > newPos.getY()){
							drawArrow(
									x1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									y1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.NORTH_EAST,
									graphics
							);
						}
					}
					
					if(agentPos.getX() == newPos.getX()){
						if(agentPos.getY() < newPos.getY()){
							drawArrow(
									x1,
									y1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.SOUTH,
									graphics
							);
						}else{
							drawArrow(
									x1,
									y1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.NORTH,
									graphics
							);
						}
					}
					
					if(agentPos.getX() > newPos.getX()){
						if(agentPos.getY() < newPos.getY()){
							drawArrow(
									x1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									y1 + circleSize + Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.SOUTH_WEST,
									graphics
							);
						}
						if(agentPos.getY() == newPos.getY()){
							drawArrow(
									x1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									y1,
									circleSize,
									Direction.WEST,
									graphics
							);
						}
						if(agentPos.getY() > newPos.getY()){
							drawArrow(
									x1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									y1 - circleSize - Main.RESOURCE_BLOCK_SIZE / 2,
									circleSize,
									Direction.NORTH_WEST,
									graphics
							);
						}
					}
					
					graphics.drawLine(x1, y1, x2, y2);			
				}
				agentPos = newPos;
				break;
			case OPEN:
				this.drawIntentStr(
						"OPEN",
						this._map.getPosition(instruction._subjectId), 
						graphics
				);
				break;
			case CLOSE:
				this.drawIntentStr(
						"CLOSE",
						this._map.getPosition(instruction._subjectId), 
						graphics
				);
				break;
			case UNLOCK:
				this.drawIntentStr(
						"UNLOCK", 
						this._map.getPosition(instruction._subjectId), 
						graphics
				);
				break;
			case LOCK:
				this.drawIntentStr(
						"LOCK", 
						this._map.getPosition(instruction._subjectId),
						graphics
				);
				break;
			case PICK_UP:
				this.drawIntentStr(
						"PICK_UP", 
						this._map.getPosition(instruction._subjectId), 
						graphics
				);
				break;
			default:
			}
		}
	}
	
	
	/**
	 * Draws an intent string to a selected position.
	 * 
	 * @param text
	 * @param pos
	 * @param graphics
	 */
	protected void drawIntentStr(String text, Position pos, Graphics graphics) {
		//graphics.setColor(color);//org.newdawn.slick.Color.black);
		
		if(this.positionInScreen(pos)){
			int x = this._screen.mapXToScreenX(pos.getX());
			int y = this._screen.mapYToScreenY(pos.getY());
			graphics.drawString(text, x, y);
		}
	}
	
	
	/**
	 * Draws a triangle on the coordinates x,y with the selected orientation.
	 * 
	 * @param x
	 * @param y
	 * @param length
	 * @param direction
	 * @param graphics
	 */
	protected static void drawArrow(
			int x,
			int y,
			int length,
			Direction direction,
			Graphics graphics
	) {
		float[] points = null;
		switch(direction){
		case EAST:
			points = new float[]{
				x + length,
				y,
				x - length,
				y + length,
				x - length,
				y - length,
			};
			break;
		case NORTH:
			points = new float[]{
				x,
				y - length,
				x + length,
				y + length,
				x - length,
				y + length,
			};
			break;
		case WEST:
			points = new float[]{
				x - length,
				y,
				x + length,
				y - length,
				x + length,
				y + length,
			};
			break;
		case SOUTH:
			points = new float[]{
				x,
				y + length,
				x - length,
				y - length,
				x + length,
				y - length,
			};
			break;
		case NORTH_WEST:
			points = new float[]{
				x - length,
				y - length,
				x + length,
				y - length,
				x - length,
				y + length,
			};
			break;
		case NORTH_EAST:
			points = new float[]{
				x + length,
				y - length,
				x + length,
				y + length,
				x - length,
				y - length,
			};
			break;
		case SOUTH_EAST:
			points = new float[]{
				x + length,
				y + length,
				x - length,
				y + length,
				x + length,
				y - length,
			};
		case SOUTH_WEST:
			points = new float[]{
				x - length,
				y + length,
				x - length,
				y - length,
				x - length,
				y + length,
			};
			break;
		}
		Shape shape = new Polygon(points);
		graphics.fill(shape);
	}
	
	
	/**
	 * Draws a selectionrectangle to it's required place.
	 */
	protected void drawSelection() {
		if(this._selection._x >= this._screen._x && 
		   this._selection._x <= this._screen._x + this._screen._width - 1 &&
		   this._selection._y >= this._screen._y &&
		   this._selection._y <= this._screen._y + this._screen._height - 1)
			this._imgSelection.draw(
				this._screen.mapXToScreenX(this._selection._x),
				this._screen.mapYToScreenY(this._selection._y),
				GraphicPlayState.SCALE
			);
	}
	
	
	/**
	 * 
	 * 
	 * @param position
	 * @return
	 */
 	protected boolean positionInScreen(Position position) {
 		if(position.getX() <  this._screen._x || 
 		   position.getX() >= this._screen._x + this._screen._width ||
 		   position.getY() <  this._screen._y ||
 		   position.getY() >= this._screen._y + this._screen._height)
 			return false;
 		return true;
 	}

}
